#include "../inc/transmitFile.h"
#include "../inc/main.h"
#include "../inc/md5.h"
#include "../inc/threadPool.h"
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>

#define PERM 0664  //权限
/*
 * @brief 循环接收函数
 * @param clientFileDescribe in 对方的文件描述符
 * @param totallen in 要接收的文件总长度
 * @param pData out 用于接收实际信息的buf
 * @return 非0值表示成功接收到文件的大小，-1表示对方离线接收失败
 */
int recvCycle(const int clientFileDescribe, const int totallen, void *pData){
    int ret = 0;  //接收函数返回值
    int recvlen = 0;  //已接收文件的大小
    printk("totallen = %d\n", totallen);
    while (recvlen < totallen) {
        ret = recv(clientFileDescribe, (char *)pData + recvlen, totallen - recvlen, 0);
        recvlen += ret;
        printf("recvlen = %d\n", recvlen);
        if (0 == ret){
            printk("bye\n");
            return -1;
        }
    }
    return recvlen;
}

/*
 * @brief 文件接收函数
 * @param clientFileDescribe in 对方的文件描述符
 * @param fileName in 要接收的文件名
 * @return 0表示接收成功，-1表示接收失败
 */
int getFile(int clientFileDescribe,const char* fileName){
    //工具
    int type = 0;  //传输的数据类型
    int size = 0;  //data的大小
    char data[MAX_DATA_SIZE] = {0};  //实际使用的信息
    int ret = 0;  //接收函数返回值
    int fd = 0;
    off_t fileLen = 0;  //接收文件总长度
    off_t recvLen = 0;  //已接收的文件长度
    protocol_t *pProtocol = (protocol_t *)calloc(1, sizeof *pProtocol + MAX_DATA_SIZE + 1);
    //判断是否有同名文件存在
    if (-1 != (fd = open(fileName, O_RDONLY))) {
        pProtocol->type = FILE_EXIST;
        pProtocol->size = 0;
        send(clientFileDescribe, pProtocol, getProtocolSize(pProtocol), 0);
        close(fd);
        goto run_error;
    }
    pProtocol->type = FILE_TRANS_START;
    pProtocol->size = 0;
    send(clientFileDescribe, pProtocol, getProtocolSize(pProtocol), 0);

    //接收文件名
    // recv(clientFileDescribe,&type,sizeof(type),0);
    // recv(clientFileDescribe,&size,sizeof(size),0);
    // recv(clientFileDescribe,data,size,0);
    //接收文件的md5值
    // getOneFrame(clientFileDescribe, pProtocol);
    //创建同名文件
    fd = open(fileName, O_RDWR|O_CREAT, PERM);
    if (-1 == fd) {
        printk("创建文件失败\n");
        goto run_error;
    }
    //接收文件的大小信息
    getOneFrame(clientFileDescribe, pProtocol);
    memcpy(&fileLen, pProtocol->data, sizeof fileLen);
    printk("fileLen = %ld\n",fileLen);

    //接收文件内容
    //小文件不超过100M
    if(fileLen <= (1 << 20) * 100){
        ret = ftruncate(fd,fileLen);
        ERROR_CHECK(ret,-1,"ftruncate");
        while(recvLen < fileLen) {
            memset(data,0,sizeof(data));
            recv(clientFileDescribe, &type, sizeof type, MSG_WAITALL);
            recv(clientFileDescribe, &size, sizeof(size), MSG_WAITALL);
            ret = recvCycle(clientFileDescribe, size, data);
            recvLen += ret;
            float rate = (float)recvLen/fileLen * 100;
            printk("rate = %5.2f %%\n", rate);
            fflush(stdout);
            if(0 == size){
                break;
            }
            write(fd,data,ret);
        }
    }
    //大文件超过100M
    else{
        ret = ftruncate(fd, fileLen);
        ERROR_CHECK(ret, -1, "ftruncate");
        char* pMap = (char *)mmap(NULL, fileLen, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
        ERROR_CHECK(pMap, (char *)-1, "mmap");
        ret = recvCycle(clientFileDescribe, fileLen, pMap);
        munmap(pMap, fileLen);
    }
    printk("get file %s success!\n", fileName);
    close(fd);
run_error:
    free(pProtocol);
    return 0;
}

#define MD5_SIZE        16
#define MD5_STR_LEN     (MD5_SIZE * 2)

//客户端下载服务端文件到本地
int putFile(int clientFileDescribe, const char *fileName){
    protocol_t *pProtocol = (protocol_t *)calloc(1, sizeof *pProtocol + MAX_DATA_SIZE + 1);

    off_t offset = 0;
    memset(pProtocol, 0, sizeof *pProtocol + MAX_DATA_SIZE);
    pProtocol->size = strlen(fileName);
    /* strcpy(pProtocol->data, fileName); */
    getOneFrame(clientFileDescribe, pProtocol);
    // 获取客户端发送的偏移量
    if(pProtocol->type == FILE_OFFSET){
        // offset = atoi(pProtocol->data);
        memcpy(&offset, pProtocol->data, sizeof offset);
    }
    // 打开文件
    int fd = open(fileName, O_RDWR);
    if (-1 == fd) {
        printk("传输失败，请检查文件名\n");
        goto exit_error;
    }
    // 发送文件名
    // send(clientFileDescribe, &pProtocol, getProtocolSize(pProtocol), 0);
    // 发送md5
    // if(pProtocol->type == FILE_MD5){
    //     int ret;
    //     /* const char *file_path = "你的filename"; */
    //     char md5_str[MD5_STR_LEN + 1];
    //     memset(md5_str,0,sizeof(md5_str));
    //     ret = Compute_file_md5(fileName, md5_str);
    //     if (0 == ret){
    //         printk("[file - %s] md5 value:", fileName);
    //         printk("%s\n", md5_str);
    //         /* 再给对方发送一份 */
    //         pProtocol->type = FILE_MD5;
    //         pProtocol->size = strlen(md5_str);
    //         strcpy(pProtocol->data, md5_str);
    //         send(clientFileDescribe, pProtocol, getProtocolSize(pProtocol), 0);
    //     }
    // }
    //获取文件信息
    struct stat fileInfo;
    bzero(&fileInfo, sizeof(fileInfo));

    fstat(fd, &fileInfo);
    pProtocol->type = FILE_SIZE;
    pProtocol->size = sizeof(fileInfo.st_size);
    memcpy(pProtocol->data, &fileInfo.st_size, pProtocol->size);

    //发送文件大小
    if(pProtocol->type == FILE_SIZE){
        send(clientFileDescribe, pProtocol, getProtocolSize(pProtocol), 0);
        printk("file length = %ld\n", fileInfo.st_size);
    }

    //发送文件内容
    pProtocol->type = FILE_DATA;
    if (fileInfo.st_size <= (1 << 20) * 100){
        lseek(fd, offset, SEEK_SET);
        int sum = 0;
        while(sum < fileInfo.st_size) {
            pProtocol->size = read(fd, pProtocol->data, MAX_DATA_SIZE);
            int ret = send(clientFileDescribe, pProtocol, getProtocolSize(pProtocol), 0);
            sum += (pProtocol->size);
            if(-1 == ret){
                break;
            }
            if(0 == pProtocol->size){
                break;
            }
        }
    }
    else{
        char *pMap=(char *)mmap(NULL, fileInfo.st_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);//内存映射，
        ERROR_CHECK(pMap, (char *)-1, "mmap");
        send(clientFileDescribe, pMap + offset, fileInfo.st_size - offset, 0);//发送
        munmap(pMap, fileInfo.st_size);//解除映射
    }
    printk("send %s success!\n", fileName);
    close(fd);
exit_error:
    free(pProtocol);
    return 0;
}
